<?php
/**
*
*
*/
abstract class Yz_Application_Abstract
    extends Yz_Class
    implements Yz_Application_Interface
{

    const LOG_CLASS = 'Yz_Log_Log';

    /**
    *   @var self::
    */
    private static $instance;

    /**
    *   @var self::
    */
    protected static $namespaces;

    /**
    *   @var Yz_Config
    */
    protected $config;

    /**
     *  @var array
     */
    protected $configFiles = array(
        'config.php',
        );


    /******************************
    ***      Public methods     ***
    *******************************/


    /**
    *   XXX There is a chance that this method get called before ::main() and there be no $instance
    *   @return Yz_Config
    */
    public static function getConfig()
    {
        return static::$instance->config;
    }


    /**
     *  @return Yz_Array
     *
     */
    public static function getInstalledNamespaces()
    {
        debug();
        $n =& static::$namespaces;
        if (null === $n) {
            $n = static::getConfig()->fetchArray('namespaces');
            if (null === $n) {
                $n = new Yz_Array();
            }
            $n->push('Yz');
        }
        return $n;
    }


    /**
     *  @return Yz_Array
     *
     */
    public static function getAppNamespace()
    {
        debug();
        return static::getInstalledNamespaces()->offsetGet(0);
    }

    /**
    *   XXX There is a chance that this method get called before ::main() and there be no $instance
    *   @param string selector
    *   @return module object
    *   @todo namespace select
    */
    public static function selectModule( $moduleName )
    {
        debug();

        static $modules = array();

        $moduleName = ucfirst(Yz_String::identifier($moduleName));
        if (array_key_exists($moduleName, $modules)) {
            return $modules[$moduleName];
        }

        foreach (static::getInstalledNamespaces() as $ns) {
            $moduleClass = sprintf("%s\\modules\\%s\\Module",
                ucfirst($ns),
                $moduleName
            );
            $class = static::loadClass($moduleClass);
            if (false !== $class) {
                $module = $class::getInstance();
                $modules[$moduleName] = $module;
                return $module;
            }
        }
        $ex = new Yz_Exception_ModuleNotFound();
        throw $ex->withArgs( array("moduleName" => $moduleName) );
    }

    /**
     *  User-API method to load a class or verify that it's loaded
    *   @param string className
    *   @return className|false
    */
    public static function loadClass( $className )
    {
        return Autoloader::loadClass($className);
    }

    /**
    *   @return int code: 0 = no error; code > 0: error code
    */
    public final static function main()
    {
        static::$instance = $app = new static();
        return $app();
    }

    /**
     *
     *
     */
    public function __destruct()
    {
        debug(  sprintf("Included files: %d, classes: %d, interfaces: %d",
            count(get_included_files()),
            count(get_declared_classes()),
            count(get_declared_interfaces())
        ));
        debug("Used memory: ".memory_get_usage());
        debug("END_LOG");
    }


    /******************************
    ***     Protected methods   ***
    *******************************/

    /**
    *   @return int
    */
    abstract protected function __invoke();

    /**
    *   @return (ignored)
    */
    abstract protected function displayErrorMessage( $message );

    /**
    *   Perform some common (SAPI independent) basic initialization
    *
    */
    protected function __construct()
    {
        class_alias( get_class($this), 'Yz' );
        defined( 'YZ_TIME' ) or define( 'YZ_TIME', microtime(true) );
        defined( 'YZ_ROOT' ) or define( 'YZ_ROOT', realpath('.') . '/' );

        $this
            ->initLog()
            ->initErrorHandlers()
            ->initAssert()
            ->initConfig();

    } // __construct


    /**
    *
    */
    protected function initConfig()
    {
        $myconfig = new Yz_Config( null, 'App.Config' );
        foreach ($this->configFiles as $cf) {
            $file = new Yz_File($cf);
            if (! $file->isFile()) {
                continue;
            }
            debug("Include config file '{$cf}'");
            $configString = include( "$cf" );
            if ( "" !== "$configString" ) {
                $config = Yz_Config_Loader::load( $configString, 'App.Config', $myconfig );
            }
        }
        $this->config = $myconfig;
        return $this;
    }

    /**
    *
    */
    protected function initErrorHandlers()
    {
        error_reporting( E_ALL | E_STRICT );

        // set the error handler
        set_error_handler( array( $this, 'errorHandler' ) );

        // set the exception handler
        set_exception_handler( array( $this, 'exceptionHandler' ) );
        return $this;
    }

    /**
    *
    */
    protected function initAssert()
    {
        // Activate assert w/o warnings and make it quiet
        assert_options(ASSERT_ACTIVE, 1);
        assert_options(ASSERT_WARNING, 0);
        assert_options(ASSERT_QUIET_EVAL, 1);
        // set the exception handler
        assert_options(ASSERT_CALLBACK, array( $this, 'assertHandler' ) );
        return $this;
    }

    /**
    *
    */
    protected function initLog()
    {
        $class = static::LOG_CLASS;
        static::loadClass($class);
        class_alias( $class, 'Log' );
        Log::init();
        // create two log writers: YZ_LIB/app.log and STDERR
        Log::addWriter(
            new Yz_Log_Writer_Text( YZ_LIB . 'app.log' ),
            Log::DEBUG4
        );
        Log::addWriter(
            new Yz_Log_Writer_Text( 'php://stderr' ),
            Log::CRITICAL
        );
        Log::log( "BEGIN_LOG", Log::INFO );
        return $this;
    }


    /**
    *
    */
    public function errorHandler( $errno, $errstr, $errfile, $errline )
    {

        /* ALWAYS log ALL warnings!
        * // if a statement has warnings disabled by @, mark this by "quiet"
        */
        $quiet = 0 === error_reporting()
            ? " (quiet)"
            : "";
        switch ( $errno ) {
            case E_WARNING:
            case E_USER_WARNING:
                $error = 'WARNING';
                break;
            case E_NOTICE:
            case E_USER_NOTICE:
                $error = 'NOTICE';
                break;
            case E_STRICT:
                $error = 'STRICT';
                break;
            case E_DEPRECATED: // since 5.3.0
            case E_USER_DEPRECATED:
                $error = 'DEPRECATED';
                break;
            case E_RECOVERABLE_ERROR: // since 5.2.0
                throw new Yz_Exception("PHP Recoverable error '$errstr' at $errfile@$errline");
            default:
                throw new Yz_Exception("Unrecognized PHP warning code: $errno");
        } // switch
        Log::log( "$errfile@$errline: PHP $error$quiet: $errstr", Log::ERROR );
        return;
    } // errorHandler()



    /**
    *   exceptionHandler() : Define default exception handler
    *
    **/
    public function exceptionHandler( Exception $ex )
    {
        Log::log( "Exception: " . get_class($ex), Log::ERROR );

        if( $ex instanceof Yz_Exception ) {
            $msg = $ex->toString( false );
            $exText = $ex->toString( true );
        } else {

            $msg = sprintf( "Unhandled Exception with code '%s'\n in file '%s' at line '%d':\n%s",
                          get_class( $ex ),
                          $ex->getCode(),
                          $ex->getFile(),
                          $ex->getLine(),
                          $ex->getMessage() ) ;
            $exText = "Fatal exception: $msg\nStack trace: " . $ex->getTraceAsString();
        }

        Log::log( "Fatal exception:\n$exText", Log::FATAL );

#        if ( self::$DEBUG ) {
            $msg = $exText;
#        }

        $this->displayErrorMessage( $msg );
        exit( 1 );
    } // exceptionHandler

    /**
    *   exceptionHandler() : Define default exception handler
    *
    **/
    public function assertHandler( $file, $line, $code )
    {
        $msg = "Assertion failed in {$file} at line {$line}: {$code}";
        Log::log( $msg );

        $this->displayErrorMessage( $msg );
        exit( 1 );
    } // exceptionHandler


    public static function initI18N()
    {
        debug();
        if (! class_exists('I18N', false)) {
            $i18n = Yz_I18N_Default::getInstance();
            class_alias($i18n->className(),'I18N');
        }
        return;
    }



} // class

